/*!
 * @author  Vladimir Kerimov
 * @file    opt.h
 * @version 1.0
 * @brief   Optional value type
 * @date    2013
 */

/*
 *    ______   __
 *   /  ___/  /__/   __  ___      _____        _____     __    __     _____        __  ___      _____     __  ___
 *  |  |___    __   /  |/   \    / __  \      /  _  \   |  |  |  |   /  _  \      |  |/   \    /  _  \   |  |/   \
 *   \___  \  /  |  |   __   |  | /  __/     |  / \  |  |  |  |  |  |  / \  |     |   __   |  |  / \  |  |   __   |
 *  ____/  /  |  |  |  /  \  |  |   /___     |  \_/  |  |   \_|  |  |  \_/  |     |  /  \  |  |  \_/  |  |  /  \  |
 * /______/   |__/  |__|  |__/   \_____/      \___   |   \___/|__|   \___/|_|     |__|  |__|   \_____/   |__|  |__|
 *                                                |  |
 *                                                |__/
 *
 */

#pragma once

#include <qua/api.h>
#include <qua/stdfwd.h>
#include <utility>

namespace qua
{
    const std::nullptr_t null = nullptr;

    QUAZAR_API void throw_value_is_null();

    template< typename T >
    class opt
    {
    public:
        opt()
            : m_value( nullptr )
        {
        }

        opt( T const& value )
            : m_value( reinterpret_cast<T*>(m_buffer) )
        {
            *m_value = value;
        }

        opt( T&& temp )
            : m_value( reinterpret_cast<T*>(m_buffer) )
        {
            *m_value = std::move( temp );
        }

        opt( opt const& other )
            : m_value( other.is_null() ? nullptr : reinterpret_cast<T*>(m_buffer) )
        {
            if( m_value ) set_value( other.get_value() );
        }

        opt( opt&& temp )
            : m_value( temp.is_null() ? nullptr : reinterpret_cast<T*>(m_buffer) )
        {
            if( m_value ) set_value( std::move( temp.get_value() ) );
        }

        opt& operator = ( T const& value ) {
            set_value( value );
            return *this;
        }

        opt& operator = ( T&& temp ) {
            set_value( std::move( temp ) );
            return *this;
        }

        opt& operator = ( std::nullptr_t ) {
            set_null();
            return *this;
        }

        opt& operator = ( opt const& other ) {
            other.is_null() ? set_null() : set_value( other.get_value() );
            return *this;
        }

        opt& operator = ( opt&& temp ) {
            temp.is_null() ? set_null() : set_value( std::move( temp.get_value() ) );
            return *this;
        }

        bool is_null() const {
            return m_value == nullptr;
        }

        bool is_not_null() const {
            return m_value != nullptr;
        }

        bool operator ! () const {
            return m_value == nullptr;
        }

        T const& get_value() const {
            if( !m_value )
                throw_value_is_null();
            return *m_value;
        }

        void set_value( T const& value ) {
            m_value = reinterpret_cast<T*>( m_buffer );
            *m_value = value;
        }

        void set_value( T&& temp ) {
            m_value = reinterpret_cast<T*>( m_buffer );
            *m_value = std::move( temp );
        }

        void set_null() {
            m_value = nullptr;
        }

        T& get_value() {
            if( !m_value )
                throw_value_is_null();
            return *m_value;
        }

        T const& operator * () const {
            return get_value();
        }

        T& operator * () {
            return get_value();
        }

        template< typename OtherT >
        bool operator == ( opt<OtherT> const& other ) const {
            return ( is_null() && other.is_null() ) || get_value() == other.get_value();
        }

        template< typename OtherT >
        bool operator != ( opt const& other ) const {
            return !( *this == other );
        }

        bool operator == ( T const& value ) const {
            return is_not_null() && get_value() == value;
        }

        bool operator != ( T const& value ) const {
            return !( *this == value );
        }

        bool operator == ( std::nullptr_t ) const {
            return is_null();
        }

        bool operator != ( std::nullptr_t ) const {
            return is_not_null();
        }

    private:
        T*   m_value;
        char m_buffer[ sizeof(T) ];
    };

    template< typename T >
    std::ostream& operator << ( std::ostream& output, opt<T> const& value )
    {
        return value.is_null() ? output : ( output << value.get_value() );
    }

    template< typename T >
    std::istream& operator >> ( std::istream& input, opt<T>& value )
    {
        if( value.is_null() ) value.set_value( T() );
        return input >> value.get_value();
    }
}
